package com.atguigu.gulimall.product.web;

import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Controller
public class IndexController {

    @Autowired
    CategoryService categoryService;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @GetMapping({"/", "/index.html"})
    public String indexPage(Model model){
        List<CategoryEntity> level1Categorys = categoryService.getLevel1Categorys();
        model.addAttribute("categorys",level1Categorys);
        return "index";
    }

    @GetMapping("/index/catalog.json")
    @ResponseBody
    public Map<String, List<Catelog2Vo>> getCatalogJson() {

        Map<String, List<Catelog2Vo>> catalogJson = this.categoryService.getCatalogJson();
        return catalogJson;
    }



    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        /**
         * https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
         * 可重入锁 Reentrant Lock
         *      // 加锁以后10秒钟自动解锁
         *      // 无需调用unlock方法手动解锁
         *      lock.lock(10, TimeUnit.SECONDS);
         *
         *      // 尝试加锁，最多等待100秒，上锁以后10秒自动解锁
         *      boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
         *
         * 公平锁 Fair Lock
         *      RLock fairLock = redisson.getFairLock("anyLock");
         *
         * 读写锁 ReadWriteLock
         *      // 保证一定能读到最新数据，修改期间，写锁是一个排他锁（互斥锁），读锁是一个共享锁。写锁没释放，读锁必须等待
         *      RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
         *      rwlock.readLock().lock();
         *      rwlock.writeLock().lock();
         *
         * 信号量 Semaphore
         *      // 也可以用作限流
         *      RSemaphore semaphore = redisson.getSemaphore("semaphore");
         *      semaphore.acquire();
         *      semaphore.release();
         *
         * 闭锁 CountDownLatch
         *      RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
         *      latch.trySetCount(3);
         *      latch.await();
         *      latch.countDown();
         *
         *
         */

        /*
        *   缓存一致性，保证最终一致性
            同时修改缓存中的数据  双写模式出现问题：两个线程更新同一个数据，先更新数据库的线程阻塞导致后写入缓存，导致数据不一致
            redis.del("catalogJSON") 等待下次主动查询进行更新。出现问题：更新缓存数据是脏数据
            解决方案：
                实时性、一致性不高的，缓存加过期时间，最多添加读写锁。
                实时性、一致性高的，查询数据库
                使用Canal更新缓存，数据库更新时，Canal订阅binlog，写到redis中

            使用Canal解决数据异构问题，订阅用户访问记录表、商品信息表数据进行分析计算生成用户推荐表
            我们系统一致性解决方案：
                1、缓存的所有数据都有过期时间，数据过期下一次查询触发主动更新
                2、读写数据的时候，加上【分布式的读写锁】，经常写，经常读

        *
        */
        // 1 获取一把锁，只要锁的名字一样就是一把锁
        RLock lock = redissonClient.getLock("hello");
        // 2 加锁
        //   lock.lock(); // 阻塞式等待，默认加锁时间30s
        // 1） 锁会自动续期，如果业务超长，运行期间会自动给锁续上新的30s，不用担心业务时间长锁过期
        // 2） 加锁的业务只要运行完成，就不会给当前锁续期，即使不手动解锁，默认也会30s之后自动删除

        // 10秒后自动解锁，自动解锁时间一定要大于业务的执行时间，时间到了不会续期
        // 1、如果我们传递了锁的超时时间，就发送给redis执行脚本，进行展缩，默认超时就是我们指定的时间
        // 2、若果我们未指定锁的超时时间，就使用30s【看门狗的默认时间】
        //      只要占锁成功，就会启动一个定时任务【重写设置过期时间，新的过期时间就是看门狗的默认时间】，每隔10s自动续期
        //      internalLockLeaseTime【看门狗时间】3，10s，建议使用设置时间

        lock.lock();

        lock.lock(10, TimeUnit.SECONDS);
        System.out.println("加锁成功"+Thread.currentThread().getId());

        try {
            Thread.sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 3 解锁 假设解锁代码没有运行，也不会产生死锁
            lock.unlock();
            System.out.println("释放锁。。。" + Thread.currentThread().getId());
        }
        return "hello";
    }

    /**
     * 只要有写存在，必须等待
     * @return
     */
    @GetMapping("/wirte")
    @ResponseBody
    public String wirte() {

        String s = "";
        RReadWriteLock lock1 = redissonClient.getReadWriteLock("lock1");

        try {
            lock1.writeLock().lock();
            s = UUID.randomUUID().toString();
            stringRedisTemplate.opsForValue().set("h1", s);
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock1.writeLock().unlock();
        }
        return s;
    }

    @GetMapping("/red")
    @ResponseBody
    public String red() {
        String s ="";
        RReadWriteLock lock1 = redissonClient.getReadWriteLock("lock1");
        try {
            lock1.readLock().lock();
            System.out.println("xx");
             s = stringRedisTemplate.opsForValue().get("h1");
            System.out.println(s);
        }finally {
            lock1.readLock().unlock();
        }

        return s;


    }

    // Semaphore 信号量测试
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException {
        RSemaphore park = redissonClient.getSemaphore("park");
        park.acquire();
        return "停车成功";
    }
    @GetMapping("/go")
    @ResponseBody
    public String go() {
        RSemaphore park = redissonClient.getSemaphore("park");
        park.release();
        return "开走了 ";
    }

    // CountDownLatch 闭锁
    @GetMapping("/door")
    @ResponseBody
    public String door() throws InterruptedException {
        RCountDownLatch park = redissonClient.getCountDownLatch("park");
        park.trySetCount(3);
        park.await();
        return "全部走完 关门成功。。。";
    }
    @GetMapping("/gogo/{id}")
    @ResponseBody
    public String gogo(@PathVariable("id") String id) {
        RCountDownLatch park = redissonClient.getCountDownLatch("park");
        park.countDown();
        return id + " 班级已经走完";
    }


}
